home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 May: Tool Chest / Developer CD Series Tool Chest (Apple Computer)(May 1999).iso / Tool Chest / Development Kits / MPW etc / MPW-GM / MPW / Examples / AExamples / Count.a next >
Encoding:
Text File  |  1998-12-03  |  15.3 KB  |  570 lines  |  [TEXT/MPS ]

  1. *-------------------------------------------------------------------------------------------
  2. *
  3. * NAME
  4. *        Count.a -- count lines and characters
  5. *
  6. * SYNOPSIS
  7. *        Count [-l] [-c] [file…]
  8. *
  9. * DESCRIPTION
  10. *        "Count" counts the lines and characters in its input, and writes the
  11. *        counts to standard output.  If no files are specified standard input is
  12. *        read.  If more than one file is specified, separate counts are written
  13. *        for each file, one per line, preceeded by the file name.  A total is also
  14. *        written following the list of files.
  15. *
  16. * COPYRIGHT
  17. *        Copyright Apple Computer, Inc. 1985-1987, 1993
  18. *        All rights reserved.
  19. *
  20. * MODIFICATION HISTORY
  21. *        1/13/93     MPH    Includes new style ("cool") Packages.a instead of
  22. *                        Traps.a and PackMacs.a
  23. *        10/15/90 JAK    No options and both options produce
  24. *                       char and line output.
  25. *-------------------------------------------------------------------------------------------
  26.  
  27.     CASE    OBJ
  28.     
  29.     INCLUDE    'intenv.a'        ; so we can get our args, open files, etc.
  30.     INCLUDE    'signal.a'        ; so we can handle 'Command-.'
  31.     INCLUDE    'TextUtils.a'    ; for _NumToString
  32.     IMPORT    INITCURSORCTL    ; to init the spinning beach ball
  33.     IMPORT    ROTATECURSOR    ; for the spinning beach ball
  34.  
  35. RC_Normal    EQU    0
  36. RC_ParmErrs EQU    1
  37. RC_Abort    EQU    2         ; Return codes
  38.  
  39. ;SIGINT    EQU    2
  40.  
  41. EOLChar    EQU    $0D        ; the Return character marks the end of line
  42.     STRING    Pascal        ; length byte strings
  43.     
  44. BufSize    EQU    1024        ; size of input buffer
  45.  
  46. * global data--these declarations outside of any module are allocated and accessed
  47. * relative to register A5
  48. Globals    RECORD
  49. ArgV    DS.L    1        ; the address of our arguments
  50. ArgC    DS.L    1        ; the number of our arguments
  51. RetCode    DC.B    RC_Normal        ; set to RC_ …
  52. CRStr    DC.W    $010D        ; a 'string' that is a return character
  53. Interrupted    DC.B    0        ; not interrupted yet
  54. progname    DS.L    1        ; the address of our name
  55. NumFiles    DC.W    0        ; the number of files to process
  56. WriteChars    DC.B    0        ; TRUE if the user wants line count
  57. WriteLines    DC.B    0        ; TRUE if the user wants char count
  58. Opts        DC.B    0        ; TRUE if user has selected either line or char
  59. LineCount    DC.L    0
  60. CharCount    DC.L    0
  61. TotalLines    DC.L    0
  62. TotalChars    DC.L    0
  63. Max    DC.B    5        ; length of 'Total' string, or the longest filename
  64. myBuf    DS.B    BufSize        ; for reading from the file
  65. curByte    DC.W    -1        ; the current offset in myBuf
  66. lastByte    DS.W    1        ; last valid byte in myBuf
  67.     ENDR
  68.  
  69.  
  70. *******************************************************************
  71. *  ROUTINE        WriteStrings
  72. *  FUNCTION        calls write for an arbitrary number of strings
  73. *  INPUT        a NIL pointer on stack, followed by arbitrary number of string pointers,
  74. *        and the file descriptor
  75. *  OUTPUT        none
  76. *  NOTES        PROCEDURE    WriteStrings (NIL, Str^ …,FD);
  77. *******************************************************************
  78.  
  79. WriteStrings    PROC
  80.     Link    A6,#0        ; set up a stack frame
  81.     Move.L    A2,-(SP)        ; and save one permanent register
  82.     LEA    8(A6),A2        ; point A2 at first (last) parameter
  83.  
  84. * next, create a call block for the write routine on the stack
  85.     Clr.L    -(SP)        ; set the length to zero
  86.     SubQ    #8,SP        ; make room for the buffer and fd
  87.     Move.L    (A2)+,(SP)        ; put the file descriptor in its place
  88.  
  89. * now pull the arguments off the stack and write them out
  90. @1    Move.L    (A2)+,D0        ; get the string pointer
  91.     BEQ.S    @0        ; the list of strings is NIL terminated
  92.     Move.L    D0,A0        ; move the pointer so we can use it
  93.     Move.B    (A0)+,11(SP)    ; to move the length byte into the length arg
  94.     Move.L    A0,4(SP)        ; move the pointer into the buffer arg
  95.     JSR    write        ; write it--CASE is significant
  96.     BRA.S    @1        ; and try again
  97.  
  98. *  done writing.  Clean up the stack and return
  99. @0    Move.L    A2,A1        ; we still need this
  100.     Move.L    -4(A6),A2        ; restore A2
  101.     UNLK    A6        ; throw away the scratch stack stuff
  102.     Move.L    (SP),A0        ; get the return address
  103.     Move.L    A1,SP        ; throw away the parameters
  104.     JMP    (A0)        ; and bail out
  105.     ENDPROC
  106.  
  107.  
  108. *******************************************************************
  109. *  ROUTINE        Stop
  110. *  FUNCTION        terminates execution
  111. *  INPUT        Message(A6)--error message to display on exit
  112. *  OUTPUT        Tool execution is terminated--return to MPW shell
  113. *  NOTES        call with a JMP, not a JSR--it doesn't return to caller anyway
  114. *******************************************************************
  115.  
  116. Stop    PROC
  117.  
  118. *  don't bother to save permanent registers--we're never going back to the caller
  119.     WITH    Globals
  120.     MoveQ    #0,D0
  121.     Move.B    RetCode,D0        ; we'll return this status
  122.     TST.B    Interrupted
  123.     BEQ.S    @1
  124.     Move.B    #RC_Abort,D0    ; unless we were interrupted
  125.  
  126. @1    Move.L    D0,-(SP)
  127.     JSR    exit        ; (does not return)
  128.     ENDWITH
  129.     ENDPROC
  130.  
  131.  
  132. *******************************************************************
  133. *  ROUTINE        Intr
  134. *  FUNCTION        sets the global Interrupted to TRUE--passed to the Runtime routine
  135. *  INPUT
  136. *  OUTPUT        Interrupted is set TRUE
  137. *  NOTES
  138. *******************************************************************
  139.  
  140. Intr    PROC
  141.     ST    Globals.Interrupted
  142.     RTS
  143.     ENDPROC
  144.  
  145.  
  146. *******************************************************************
  147. *  ROUTINE        SyntaxError
  148. *  FUNCTION        Report a syntax error for the command line
  149. *  INPUT        above(A7)--pointers to strings to append to the error message
  150. *  OUTPUT        displays error message and calls Stop to terminate program execution
  151. *  NOTES        call with a JMP, not a JSR--it doesn't return anyway
  152. *******************************************************************
  153.  
  154. SyntaxError    PROC
  155.  
  156.     WITH    Globals
  157.     PEA    #' - '
  158.     Move.L    progName,-(SP)
  159.     PEA    #'### '
  160.     PEA    DiagnosticFD
  161.     JSR    WriteStrings        ; finish writing the error line
  162.     CLR.L    -(SP)
  163.     PEA    CrStr
  164.     PEA    #' [-l] [-c] [files…].'
  165.     Move.L    progName,-(SP)
  166.     PEA    #'# Usage - '
  167.     PEA    DiagnosticFD
  168.     JSR    WriteStrings        ; and write the 'usage' line
  169.     JMP    Stop
  170.     ENDWITH
  171.     ENDPROC
  172.  
  173.  
  174.  
  175. *******************************************************************
  176. *  ROUTINE        LetterOpt
  177. *  FUNCTION        Set a letter    option
  178. *  INPUT        D0--char
  179. *        D4--ArgVIndex
  180. *        A1--address of current option
  181. *  OUTPUT        if char = valid option, set option flag, else syntaxerror
  182. *  NOTES        PROCEDURE    LetterOpt(Opt: Char; VAR ArgVIndex: Integer);
  183. *        ArgVIndex can be updated by this routine to skip arguments to options
  184. *******************************************************************
  185.  
  186. LetterOpt    PROC
  187.     Cmp.B    #'l',D0
  188.     BEQ.S    @0
  189.     Cmp.B    #'L',D0        ; -l?
  190.     BNE.S    @1
  191. @0    ST    Globals.WriteLines    ; means only lines
  192.     ADDQ.B    #1,Globals.Opts    ; yes, an option has been selected
  193.     RTS
  194. @1    Cmp.B    #'c',D0        ; -c?
  195.     BEQ.S    @2
  196.     Cmp.B    #'C',D0
  197.     BNE.S    @3
  198. @2    ST    Globals.WriteChars    ; means only characters
  199.     ADDQ.B    #1,Globals.Opts    ; yes, an option has been selected
  200.     RTS
  201. @3    Clr.L    -(SP)        ; otherwise its a bad option
  202.     PEA    Globals.CRStr
  203.     PEA    #'" is not an option.'
  204.     Move.L    A1,-(SP)        ; pointer to current option
  205.     PEA    #'"'                                    ; the leading quote around the option
  206.     JMP    SyntaxError
  207. *  SyntaxError never returns
  208.     ENDPROC
  209.  
  210.  
  211. *******************************************************************
  212. *  ROUTINE        Init
  213. *  FUNCTION        Tool initalization
  214. *  INPUT
  215. *  OUTPUT
  216. *  NOTES        PROCEDURE Init;
  217. *******************************************************************
  218.  
  219. Init    PROC
  220.  
  221. ForPascal    EQU    1
  222.  
  223. InitSF    RECORD    {OldA6},DECREMENT
  224. ShellRet    DS.L    1
  225. RetAddress    DS.L    1
  226. OldA6    DS.L    1
  227. EnvP    DS.L    1
  228. Size    EQU    *
  229.     ENDR
  230.  
  231.     WITH    Globals
  232.     Link    A6,#InitSF.Size
  233.     PEA    ForPascal        ; optimized Move.L #1,-(SP)
  234.     PEA    InitSF.EnvP(A6)
  235.     PEA    ArgV
  236.     PEA    ArgC
  237.     Move.L    InitSF.ShellRet(A6),-(SP)
  238.     JSR    _RTInit        ; get things set up
  239.     LEA    InitSF.Size(A6),SP    ; throw away the arguments
  240.     PEA    Intr        ; our interrupt handler
  241.     Move.L    #SIGINT,-(SP)
  242.     JSR    signal        ; so we can handle user interrupts
  243. * D0 has handle to prevSig, which we will ignore
  244.     LEA    InitSF.Size(A6),SP    ; throw away the arguments
  245.  
  246.     MoveM.L    A2/D3-D4,-(SP)    ; let's do some ArgV processing
  247.     Move.L    ArgV,A2
  248.     Move.L    ArgC,D3
  249.     Move.L    (A2)+,progName    ; we now have a global that points to our name
  250.     Move.B    #RC_ParmErrs,RetCode
  251.     MoveQ    #0,D4        ; ArgVIndex := 0;
  252. @0    AddQ    #1,D4
  253.     Cmp.L    D4,D3
  254.     BLE.S    DoneArgOptions
  255.     Move.L    (A2)+,A0        ; get the next arg
  256.     Move.L    A0,A1        ; keep a pointer to the start of the string
  257.     Move.B    (A0)+,D1        ; get the len
  258.     BEQ.S    @0        ; arg := ''; get the next one
  259.     Move.B    (A0)+,D0
  260.     Cmp.B    #'-',D0        ; is it an option?
  261.     BNE.S    @1
  262.     Move.B    (A0)+,D0
  263.     JSR    LetterOpt
  264. * caller to LetterOpt can check if ArgIndex changed--if so, skip the increment of ArgIndex
  265.     BRA.S    @0        ; go again
  266. @1    AddQ    #1,NumFiles        ; bump the file count
  267.     Cmp.B    Max,D1        ; a new longest name?
  268.     BLE.S    @0
  269.     Move.B    D1,Max        ; a new max
  270.     BRA.S    @0
  271.  
  272. DoneArgOptions
  273.     Move.B    #RC_Normal,RetCode    ; parameters ok so far
  274.     Clr.L    -(SP)
  275.     JSR    InitCursorCtl        ; initialize the spinning cursor
  276.     Tst.B    Interrupted        ; user break yet?
  277.     BEQ.S    @3
  278.     JMP    Stop
  279. @3    MoveM.L    (SP)+,A2/D3-D4
  280.     UNLK    A6
  281.     RTS
  282.     ENDWITH
  283.     ENDPROC
  284.  
  285. *******************************************************************
  286. *  ROUTINE        PrintCount
  287. *  FUNCTION        writes the filename (if needed), linecount and/or charcount to standard output
  288. *  INPUT        pointer to the filename in A2 (if counting multiple files)
  289. *  OUTPUT
  290. *  NOTES
  291. *******************************************************************
  292.  
  293. PrintCount    PROC
  294. PrintSF    RECORD    0,DECREMENT
  295. LineBuf    DS.B    256
  296. tempStr    DS.B    10
  297. MaxBlanks    DS.B    1
  298.     ALIGN
  299. Size    EQU    *
  300.     ENDR
  301.  
  302.     LINK    A6,#PrintSF.Size
  303.     MoveM.L    D6/A3,-(SP)
  304.  
  305.     Move.W    #(256/4)-1,D0    ; fill LineBuf with blanks
  306.     Move.L    #'    ',D1
  307.     LEA    PrintSF.LineBuf(A6),A0
  308. @0    Move.L    D1,(A0)+
  309.     DBRA    D0,@0
  310.  
  311.     WITH    Globals
  312.     MoveQ    #3,D6        ; skip first three blanks
  313.     LEA    4+PrintSF.LineBuf(A6),A3    ; A3 is the current offset into lineBuf
  314.     Cmp.W    #1,NumFiles        ; >1 if more than one file
  315.     BLE.S    noName
  316.  
  317.     Move.L    D7,A0        ; D7 points to the current filename
  318.     MoveQ    #0,D1
  319.     Move.B    (A0)+,D1        ; get the length byte
  320.     Add.B    D1,D6        ; update the new length
  321.     MoveQ    #0,D0
  322.     Move.B    Max,D0        ; Max is the longest name
  323.     Sub.B    D1,D0        ; D0 is how much shorter current is than max
  324.     AddQ    #3,D0
  325.     Add.B    D0,D6        ; update the counter
  326.     BRA.S    @2        ; zero base the length
  327. @1    Move.B    (A0)+,(A3)+
  328. @2    DBRA    D1,@1        ; move in the filename
  329.     Add.W    D0,A3        ; and update our roving pointer
  330.  
  331.  
  332. noName
  333.     ENTRY    DoLines,DoChars,WriteBuf
  334.     ; if no options selected, print both lines and chars.
  335.     TST.B    Opts
  336.     BNE.S    @0
  337.      
  338.      JSR    DoLines            ; insert lines into buffer
  339.      JSR    DoChars            ; insert chars into buffer
  340.      BRA.S    @Exit
  341.  
  342. @0    TST.B    WriteLines        ; do we want to print the line count?
  343.     BEQ.S    @1
  344.      JSR    DoLines            ; insert lines into buffer
  345. @1    TST.B    WriteChars
  346.     BEQ.S    @Exit
  347.      JSR    DoChars            ; insert chars into buffer
  348.  
  349. @Exit    
  350.     Move.B    D6,PrintSF.lineBuf(A6)    ; set the length byte
  351.  
  352.     CLR.L    -(SP)        ; set up the stack for WriteStrings
  353.     PEA    CRStr
  354.     PEA    PrintSF.linebuf(A6)
  355.     PEA    OutputFD
  356.     JSR    WriteStrings
  357.     MoveM.L    (SP)+,D6/A3
  358.     UNLK    A6
  359.     RTS
  360.  
  361.         
  362. DoLines         
  363.     Move.L    lineCount,D0
  364.     add.w    #10,D6        ; update counter
  365.     JMP        WriteBuf
  366.     
  367. DoChars
  368.     MOVE.L    charcount,D0
  369.     add.w    #13,D6        ; add field length and 3 blanks to counter
  370.     JMP        WriteBuf
  371.  
  372. WriteBuf    
  373.     LEA    PrintSF.tempStr(A6),A0
  374.     _NumToString
  375.  
  376.     MoveQ    #0,D1
  377.     Move.B    (A0)+,D1
  378.     MoveQ    #10,D0        ; we'll say this field is 10 long
  379.     Sub.B    D1,D0        ; D0 := field length-length of numstring
  380.     Add.W    D0,A3        ; skip the extra padding
  381.  
  382.     BRA.S    @2            ; zero base by doing the DBCC first
  383. @1    Move.B    (A0)+,(A3)+
  384. @2    DBRA    D1,@1        ; move in the number
  385.     RTS
  386.     ENDWITH
  387.     ENDPROC
  388.  
  389.  
  390. *******************************************************************
  391. *  ROUTINE        PrintTotals
  392. *  FUNCTION        writes the summary line to standard output
  393. *  INPUT
  394. *  OUTPUT
  395. *  NOTES        calls PrintCount to print the totals if appropriate
  396. *******************************************************************
  397.  
  398. PrintTotals    PROC
  399.     Cmp.W    #1,Globals.numFiles
  400.     BGT.S    @0
  401.     RTS            ; do nothing if only one file
  402. @0    LEA    #'Total',A0
  403.     Move.L    A0,D7        ; our new 'filename'
  404.     Move.L    Globals.totallines,Globals.linecount
  405.     Move.L    Globals.totalchars,Globals.charcount
  406.     JSR    PrintCount        ; recycled code
  407.     RTS
  408.  
  409.     ENDPROC
  410.  
  411.  
  412. *******************************************************************
  413. *  ROUTINE        GetChar
  414. *  FUNCTION        reads from the file in hunks, and hands out a character at a time
  415. *  INPUT        fd:  long in D4--the file descriptor for the file to read
  416. *  OUTPUT        the next character in D0--zero = TRUE means end of file
  417. *  NOTES
  418. *******************************************************************
  419.  
  420. GetChar    PROC
  421.     WITH    Globals
  422.     Move.W    curByte,D1        ; get the current offset
  423.     BPL.S    @0        ; we have a valid block currently
  424. @1    PEA    BufSize        ; Move.L #BufSize,-(SP)--count
  425.     PEA    mybuf        ; where
  426.     Move.L    D4,-(SP)        ; the file descriptor
  427.     JSR    read        ; read the next block
  428.     LEA    12(SP),SP        ; clean up the stack
  429.     MoveQ    #0,D1        ; start at the beginning again
  430.     Move.W    D0,lastByte
  431.     BNE.S    @2        ; end of file?
  432.     RTS            ; pass the zero flag back to the caller
  433. @0    Move.W    lastByte,D0        ; get the last valid byte
  434. @2    Cmp.W    D0,D1
  435.     BGE.S    @1
  436.     LEA    mybuf,A0
  437.     Move.B    0(A0,D1),D0    ; read the next character
  438.     AddQ.W    #1,D1
  439.     Move.W    D1,curByte        ; update curByte
  440.     RTS
  441.     ENDWITH
  442.     ENDPROC
  443.  
  444.  
  445. *******************************************************************
  446. *  ROUTINE        CountFile (fd:filedescriptor)
  447. *  FUNCTION        counts the lines and characters in fd
  448. *  INPUT        fd:  long--the file descriptor for the file to count
  449. *  OUTPUT        charcount, linecount, totalchars, totallines updated
  450. *  NOTES
  451. *******************************************************************
  452.  
  453. CountFile    PROC
  454.     WITH    globals
  455.     CLR.L    linecount
  456.     Move.L    (SP)+,A1        ; save the return address
  457.     Move.L    (SP),D0        ; and the file descriptor
  458.     Move.L    A1,(SP)        ; return the return address
  459.  
  460.     MoveM.L    D4-D7,-(SP)
  461.     Move.L    D0,D4        ; save the fd for the getchar routine
  462.     MoveQ    #0,D7        ; initialize our counter registers
  463.     MoveQ    #0,D6
  464.  
  465. ReadLoop    JSR    getchar
  466.     BEQ.S    fileEnd        ; zero means no more bytes to read
  467.     AddQ.L    #1,D7        ; otherwise bump the char counter
  468.     Move.B    D0,D5        ; save the char in a permanent register
  469.     CMP.B    #EOLChar,D5    ; bump linecount?
  470.     BNE.S    ReadLoop
  471.  
  472.     AddQ.L    #1,D6        ; yes
  473.     Move.L    D6,-(SP)
  474.     JSR    RotateCursor    ; spin the ball
  475.     Tst.B    Interrupted        ; user break yet?
  476.     BEQ.S    ReadLoop        ; no--continue
  477.     JMP    Stop        ; abort mission
  478.  
  479. fileEnd    CMP.B    #EOLChar,D5    ; was the last character read a line end?
  480.     BEQ.S    @0
  481.     TST.L    D7        ; have we counted any characters
  482.     BEQ.S    @0        ; no--don't increment line count
  483.     AddQ.L    #1,D6
  484. @0    Move.L    D6,lineCount        ; update globals and leave
  485.     Move.L    D7,charCount
  486.     Add.L    D6,totallines
  487.     Add.L    D7,totalchars
  488.     MoveM.L    (SP)+,D4-D7
  489.     RTS
  490.     ENDPROC
  491.  
  492.  
  493. *******************************************************************
  494. *  ROUTINE        Count
  495. *  FUNCTION        the MAIN proc--calls Init, then processes the files
  496. *  INPUT
  497. *  OUTPUT
  498. *  NOTES
  499. *******************************************************************
  500.  
  501.  
  502. Count    MAIN
  503.     IMPORT    c2pstr,p2cstr
  504.     WITH    Globals
  505.     JSR    Init
  506.     Move.L    ArgV,A2
  507.     ADDQ    #4,A2        ; skip the program name
  508.     Move.L    (A2)+,D7        ; set the cc's
  509.     BNE.S    @0        ; if 0 files, count stdin
  510.  
  511. * CountStdIn
  512.     Clr.L    -(SP)        ; we don't need to open standard input
  513.     JSR    CountFile
  514.     JSR    PrintCount
  515.     JMP    Stop
  516.  
  517. @1    Move.L    (A2)+,D7        ; set the cc's
  518.     BEQ.S    ShowTotals        ; ArgV is NIL terminated
  519.  
  520. @0    Move.L    D7,A0
  521.     Move.B    (A0)+,D0        ; pick up the length byte
  522.     BEQ.S    @1        ; zero length--next, please
  523.     Move.B    (A0)+,D1        ; now the first charcter
  524.     Cmp.B    #'-',D1        ; an option--already handled by Init
  525.     BEQ.S    @1
  526.  
  527. * otherwise we have a file to process
  528.     Move.L    D7,-(SP)        ; convert the filename to a C string
  529.     JSR    p2cstr
  530.     PEA    O_RDONLY
  531.     Move.L    D7,-(SP)
  532.     JSR    open        ; open the file
  533.     Move.L    D0,D6        ; save the result--fd or error
  534.     JSR    c2pstr        ; love those length bytes
  535.     LEA    12(SP),SP        ; throw away the arguments
  536.     Move.L    D6,-(SP)        ; push the fd
  537.     BMI.S    BailOut        ;  an error if negative
  538.     JSR    CountFile
  539.     JSR    PrintCount
  540.     BRA.S    @1
  541.  
  542. ShowTotals
  543.     JSR    PrintTotals
  544.     JMP    Stop
  545.  
  546. BailOut    CLR.L    (SP)        ; space came from move D6 above
  547.     PEA    CRStr
  548.     Move.L    D7,-(SP)
  549.     PEA    #' - could not open file '
  550.     Move.L    progName,-(SP)
  551.     PEA    #'### '
  552.     PEA    DiagnosticFD        ; optimized Move.L #DiagnosticFD,-(SP)
  553.     JSR    WriteStrings
  554.  
  555.     CLR.L    -(SP)
  556.     PEA    CRStr
  557.     PEA    #' [-l] [-c] [files…].'
  558.     Move.L    progName,-(SP)
  559.     PEA    #'# Usage - '
  560.     PEA    DiagnosticFD        ; optimized Move.L #DiagnosticFD,-(SP)
  561.     JSR    WriteStrings
  562.     Move.B    #RC_ParmErrs,RetCode
  563.     JMP    Stop
  564.  
  565.     ENDWITH
  566.     ENDPROC
  567.  
  568.     END
  569.  
  570.